﻿module FsCoreUtils.XmlEvents

open System.Xml

type QName = 
    { ns : option<string>
      local : string }

let qn (qname : string) : QName = 
    let parts = qname.Split([| ':' |], 2)
    if parts.Length > 1 then 
        { ns = Some parts.[0]
          local = parts.[1] }
    else 
        { ns = None
          local = qname }

type XmlEvent = 
    | Elem of name : QName * attrs : Map<QName, string>
    | EndElem of QName
    | Text of string
    | CDATA of string
    | ProcessingInstruction of target : string * content : string
    | Comment of string
    | XmlDeclaration
    | DocumentType of root : string * declarations : string
    | EntityRef of string

let toSeq (r : XmlReader) : seq<XmlEvent> = 
    seq { 
        while r.Read() do
            match r.NodeType with
            | XmlNodeType.Element -> 
                let name = qn r.Name
                let isEmpty = r.IsEmptyElement
                let attrs = 
                    Map.ofSeq (seq { 
                                   while r.MoveToNextAttribute() do
                                       yield (qn r.Name, r.Value)
                               })
                yield Elem(name, attrs)
                if isEmpty then yield EndElem name
            | XmlNodeType.EndElement -> yield EndElem (qn r.Name)
            | XmlNodeType.Text -> yield Text r.Value
            | XmlNodeType.CDATA -> yield CDATA r.Value
            | XmlNodeType.ProcessingInstruction -> 
                yield ProcessingInstruction(r.Name, r.Value)
            | XmlNodeType.Comment -> yield Comment r.Value
            | XmlNodeType.XmlDeclaration -> yield XmlDeclaration
            | XmlNodeType.DocumentType -> yield DocumentType(r.Name, r.Value)
            | XmlNodeType.EntityReference -> yield EntityRef r.Name
            | _ -> yield! [] // skip
    }

let writeEvent (w : XmlWriter) (e : XmlEvent) : unit =
    let writeAttr (name, value) : unit = 
        if name.ns.IsNone then w.WriteAttributeString(name.local, value)
        else w.WriteAttributeString(name.local, name.ns.Value, value)
    match e with
    | Elem (name, attrs) -> 
        do if name.ns.IsNone then w.WriteStartElement(name.local)
           else w.WriteStartElement(name.local, name.ns.Value)
        List.iter writeAttr (Map.toList attrs)
    | EndElem name -> w.WriteEndElement()
    | CDATA text -> w.WriteCData(text)
    | _ -> ()

let writeSeq (w : XmlWriter) (xs : seq<XmlEvent>) : unit = 
    Seq.iter (writeEvent w) xs

let writeSeqAsDoc (w : XmlWriter) (xs : seq<XmlEvent>) : unit =  do
    w.WriteStartDocument ()
    writeSeq w xs
    w.WriteEndDocument ()